﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Open.Nat;
using System.Net;

namespace slCommunicator
{
    public static class UPnPForwarding
    {
        private static NatDiscoverer discoverer;
        private static IEnumerable<NatDevice> devices;
        private static string NameOfMap = localization.upnp_serviceName;

        private async static Task ForwardUPnP()
        {
            if (!Properties.Settings.Default.doConformUPnP)
            {
                Console.WriteLine(localization.upnp_conformationSkiped);
                return;
            }
            int port = Listener.AssignedPort;
            if (port == 0)
                throw new Exception();

            IPAddress myLocalIp = IPAddress.Loopback;
            foreach (var i in Dns.GetHostAddresses(Dns.GetHostName()))
                if (i.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                    myLocalIp = i;
           

            Console.WriteLine(localization.upnp_BeginDiscovery);
            discoverer = new NatDiscoverer();
            devices = await discoverer.DiscoverDevicesAsync(PortMapper.Upnp, new CancellationTokenSource(Properties.Settings.Default.UPnPDiscoveryTime));
            Console.WriteLine(localization.upnp_EndDiscovery + " " + devices.ToArray().Length);

            foreach (NatDevice dev in devices)
            {
                Console.WriteLine(localization.upnp_tableTitle);
                Console.WriteLine(localization.upnp_tableSubtitle);
                var mapping = await dev.GetAllMappingsAsync();
                var pubip = await dev.GetExternalIPAsync();
                Console.WriteLine(localization.upnp_publicIps, pubip);

                bool addUDP = true;
                bool addTCP = true;

                foreach (var routermap in mapping)
                {
                    if (routermap.PrivateIP.ToString() == myLocalIp.ToString() && routermap.PublicPort == port && routermap.PrivatePort == port)
                    {
                        Console.Write("OK");
                        if (routermap.Protocol == Protocol.Tcp) addTCP = false;
                        else addUDP = false;
                    }
                    else if (routermap.PrivatePort == port || routermap.PrivatePort == port || (routermap.PrivateIP.ToString() == myLocalIp.ToString() && routermap.Description.ToLower() == NameOfMap.ToLower()))
                    {
                        Console.Write("REM");
                        await dev.DeletePortMapAsync(routermap);
                    }
                    else
                        Console.Write("-");

                    WriteMap(routermap);
                }
                List<Mapping> desiredmappingslocal = new List<Mapping>();
                if (addUDP) desiredmappingslocal.Add(new Mapping(Protocol.Udp, myLocalIp, port, port, 1000, NameOfMap));
                if (addTCP) desiredmappingslocal.Add(new Mapping(Protocol.Tcp, myLocalIp, port, port, 1000, NameOfMap));
                foreach (var map in desiredmappingslocal)
                {
                    Console.Write("+++");
                    WriteMap(map);
                    await dev.CreatePortMapAsync(map);
                }
            }

            Console.WriteLine(localization.upnp_EndConformation);
        }

        private static void WriteMap(Mapping map)
        {
            Console.WriteLine("\t{0}\t{1}\t{2}\t{3}", map.PrivateIP, map.PrivatePort, map.PublicPort, map.Description);
        }

        public static void ConformUPnP(bool bAsync)
        {
            Task t = ForwardUPnP();
            if (!bAsync)
                t.Wait();
        }
    }
}
